Skip to content

feat(custom-block): audit-log publish, update, and unpublish events#5493

Merged
TheodoreSpeaks merged 1 commit into
stagingfrom
feat/custom-block-audit-log
Jul 7, 2026
Merged

feat(custom-block): audit-log publish, update, and unpublish events#5493
TheodoreSpeaks merged 1 commit into
stagingfrom
feat/custom-block-audit-log

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Record audit-log entries for custom-block lifecycle events (previously nothing was logged when a block was published/edited/unpublished)
  • New custom_block audit resource type + custom_block.published / .updated / .deleted actions
  • Emitted from the API routes after the DB write (fire-and-forget), scoped by the source workspace with organizationId in metadata so the org audit-log page surfaces them; the .deleted verb renders as a red badge and the "Custom Block" resource filter appears automatically

Type of Change

  • New feature

Testing

Tested manually (publish/edit/unpublish → entries appear in the org audit log). @sim/audit registry-sync tests updated + passing; bun run lint, tsc, and check:api-validation clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 7, 2026 10:15pm

Request Review

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Observability-only additions after existing auth and DB writes; no change to permission rules or block behavior.

Overview
Adds audit logging for deploy-as-block lifecycle actions that previously had no org audit trail.

After successful publish (POST /custom-blocks), update (PATCH), and unpublish (DELETE), routes call recordAudit with workspace scoping and organizationId / block metadata so entries show on the org audit log. New registry entries are custom_block plus custom_block.published, .updated, and .deleted (delete is described as unpublish in the message).

authorizeManage now returns the manage context from getCustomBlockManageContext, which also loads name and type for audit payloads without an extra query. Test audit mocks mirror the new actions and resource type.

Reviewed by Cursor Bugbot for commit 03e0e23. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds audit log entries for custom-block lifecycle events (publish, update, unpublish/delete), which were previously untracked. It introduces three new action constants (custom_block.published, .updated, .deleted) and a CUSTOM_BLOCK resource type to the shared @sim/audit package, extends getCustomBlockManageContext to return type and name from the DB, and emits fire-and-forget recordAudit calls in the POST/PATCH/DELETE routes after each successful DB write.

  • New audit constants: AuditAction.CUSTOM_BLOCK_PUBLISHED/UPDATED/DELETED and AuditResourceType.CUSTOM_BLOCK added to packages/audit/src/types.ts and kept in sync in the test mock.
  • Route changes: All three route handlers (POST, PATCH, DELETE) now call recordAudit after a successful DB write, scoping the event to the source workspace and embedding organizationId in metadata so the org audit-log page can surface it.
  • authorizeManage refactor: Returns the full ManageContext (including name and type) on success, eliminating the need for an extra DB round-trip to populate the audit payload.

Confidence Score: 5/5

Safe to merge — changes are additive, fire-and-forget audit calls cannot affect the existing request/response path, and the auth guard in authorizeManage already guarantees the context fields needed for the audit payload are non-null before any audit call is reached.

The implementation is straightforward: three new string constants, a small DB select extension, and three fire-and-forget audit calls placed after successful DB writes. The recordAudit function is documented as never-throwing, so none of the new call sites can introduce a regression in the happy path or error path. The authorizeManage refactor is a clean narrowing — it returns the same union type but also carries the context needed downstream, and the existing auth logic is unchanged.

No files require special attention.

Important Files Changed

Filename Overview
packages/audit/src/types.ts Adds CUSTOM_BLOCK_PUBLISHED / UPDATED / DELETED action constants and CUSTOM_BLOCK resource type, alphabetically ordered and consistent with the existing pattern.
packages/testing/src/mocks/audit.mock.ts Mirrors the new audit constants in the test mock, keeping string values in sync with the types file and alphabetically ordered.
apps/sim/lib/workflows/custom-blocks/operations.ts Extends getCustomBlockManageContext to return type and name fields from the DB so route handlers can populate audit metadata without an extra query.
apps/sim/app/api/custom-blocks/route.ts Adds fire-and-forget recordAudit call after publishCustomBlock succeeds; passes workspaceId, actorId, and organizationId in metadata correctly.
apps/sim/app/api/custom-blocks/[id]/route.ts Refactors authorizeManage to return a typed ManageContext on success, then emits audit events after update and delete DB writes; auth guard ensures sourceWorkspaceId is non-null before either call is reached.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Route as API Route
    participant Auth as authorizeManage
    participant DB as Database
    participant Audit as recordAudit (fire-and-forget)

    Client->>Route: POST /api/custom-blocks (publish)
    Route->>DB: publishCustomBlock(...)
    DB-->>Route: block (id, name, type, ...)
    Route--)Audit: recordAudit(CUSTOM_BLOCK_PUBLISHED)
    Route-->>Client: "{ customBlock }"

    Client->>Route: PATCH /api/custom-blocks/[id] (update)
    Route->>Auth: authorizeManage(userId, id)
    Auth->>DB: getCustomBlockManageContext(id)
    DB-->>Auth: "{ organizationId, sourceWorkspaceId, type, name }"
    Auth-->>Route: "{ error: null, ctx }"
    Route->>DB: updateCustomBlock(id, patches)
    Route--)Audit: recordAudit(CUSTOM_BLOCK_UPDATED)
    Route-->>Client: "{ success: true }"

    Client->>Route: DELETE /api/custom-blocks/[id] (unpublish)
    Route->>Auth: authorizeManage(userId, id)
    Auth->>DB: getCustomBlockManageContext(id)
    DB-->>Auth: "{ organizationId, sourceWorkspaceId, type, name }"
    Auth-->>Route: "{ error: null, ctx }"
    Route->>DB: deleteCustomBlock(id)
    Route--)Audit: recordAudit(CUSTOM_BLOCK_DELETED)
    Route-->>Client: "{ success: true }"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant Route as API Route
    participant Auth as authorizeManage
    participant DB as Database
    participant Audit as recordAudit (fire-and-forget)

    Client->>Route: POST /api/custom-blocks (publish)
    Route->>DB: publishCustomBlock(...)
    DB-->>Route: block (id, name, type, ...)
    Route--)Audit: recordAudit(CUSTOM_BLOCK_PUBLISHED)
    Route-->>Client: "{ customBlock }"

    Client->>Route: PATCH /api/custom-blocks/[id] (update)
    Route->>Auth: authorizeManage(userId, id)
    Auth->>DB: getCustomBlockManageContext(id)
    DB-->>Auth: "{ organizationId, sourceWorkspaceId, type, name }"
    Auth-->>Route: "{ error: null, ctx }"
    Route->>DB: updateCustomBlock(id, patches)
    Route--)Audit: recordAudit(CUSTOM_BLOCK_UPDATED)
    Route-->>Client: "{ success: true }"

    Client->>Route: DELETE /api/custom-blocks/[id] (unpublish)
    Route->>Auth: authorizeManage(userId, id)
    Auth->>DB: getCustomBlockManageContext(id)
    DB-->>Auth: "{ organizationId, sourceWorkspaceId, type, name }"
    Auth-->>Route: "{ error: null, ctx }"
    Route->>DB: deleteCustomBlock(id)
    Route--)Audit: recordAudit(CUSTOM_BLOCK_DELETED)
    Route-->>Client: "{ success: true }"
Loading

Reviews (1): Last reviewed commit: "feat(custom-block): audit-log publish, u..." | Re-trigger Greptile

@TheodoreSpeaks TheodoreSpeaks merged commit 56cd2cf into staging Jul 7, 2026
18 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the feat/custom-block-audit-log branch July 7, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant